/**
Copyright 2020 ShenZhen Xiaoniu New Energy Inc. All Rights Reserved.
Author: saico@mosf.cn
**/

import {
  SQSClient,
  SendMessageCommand
} from '@aws-sdk/client-sqs';
import {
  LambdaClient,
  UpdateFunctionConfigurationCommand
} from '@aws-sdk/client-lambda';
import {
  IoTDataPlaneClient,
  PublishCommand
} from '@aws-sdk/client-iot-data-plane';
import {
  IoTClient,
  ListPolicyPrincipalsCommand,
  CreateKeysAndCertificateCommand,
  AttachPolicyCommand,
  AttachThingPrincipalCommand,
  CreatePolicyCommand,
  CreateThingCommand,
  DetachPolicyCommand,
  DetachThingPrincipalCommand,
  UpdateCertificateCommand,
  DescribeThingCommand,
  UpdateThingCommand,
  DeleteCertificateCommand,
  DeleteOTAUpdateCommand
} from '@aws-sdk/client-iot';
import config from './config.mjs';
import moment from 'moment-timezone';

const iotCONFIG = {region:process.env.AWS_REGION};
const sqsclient = new SQSClient(iotCONFIG);
const iotclient = new IoTClient(iotCONFIG);
const lambdaclient = new LambdaClient(iotCONFIG);
const iotdataclient = new IoTDataPlaneClient(iotCONFIG);

let listPolicyPrincipals = async ( mac ) => {
    let params = {
      policyName: mac
    };
    let iotdata;
    try {
      iotdata = await iotclient.send(new ListPolicyPrincipalsCommand(params));
    } catch (err) {
      iotdata = undefined;
    }
    return iotdata;
};

let createCert = async ( mac ) => {
    let createCertParams = {
      setAsActive: true
    };
    let certdata;
    try {
      certdata = await iotclient.send(new CreateKeysAndCertificateCommand(createCertParams));
    } catch (err) {
      certdata = undefined;
    }
    if (certdata) {
      let attachPolicyParams = {
        policyName: mac, 
        target: certdata.certificateArn 
     };
      let attachThingParams = {
        thingName: mac,
        principal: certdata.certificateArn
      };
      try {
        await iotclient.send(new AttachPolicyCommand(attachPolicyParams));
        await iotclient.send(new AttachThingPrincipalCommand(attachThingParams));
      } catch (err) {
        console.error(err);
      }
    }
    return certdata;
};

let save_NEWChargerID_ENV = async () => {
  let input = {
    FunctionName: process.env.AWS_LAMBDA_FUNCTION_NAME,
    Environment: {
      Variables: {
        'Time_Zone': process.env.Time_Zone,
        'Default_ThingType': process.env.Default_ThingType,
        'Default_Guestok': process.env.Default_Guestok,
        'Default_Gunstandard': process.env.Default_Gunstandard,
        'OTA_Version': process.env.OTA_Version,
        'OTx_Version': process.env.OTx_Version,
        'NEW_ChargerID': (Number(process.env.NEW_ChargerID)+1).toString()
      }
    }
  };
  await lambdaclient.send(new UpdateFunctionConfigurationCommand(input));
};

let issueCert = async ( mac, region, accountid ) => {
    let policydoc = config.POLICY_DOCUMENT.replace(/REGION/g, region).replace(/ACCOUNTID/g, accountid);
    let createPolicyParams = {
      policyName: mac,
      policyDocument: policydoc
    };
    let nowtm = moment(new Date().getTime()).tz(process.env.Time_Zone).format(config.SF);
    let createThingParams;
    createThingParams = {
      thingName: mac,
      attributePayload: {
        attributes: {
          'onltime':nowtm,
          'chargerid':process.env.NEW_ChargerID.toString(),
          'connected':'0',
          'offtime':nowtm,
          'gunstandard':process.env.Default_Gunstandard,
          'guestok':process.env.Default_Guestok,
          'imax':'0,0',
          'hfconst':'64,64',
          'fmver':'1.0.0',
          'debug':'1',
          'pon':'1',
          'swk':'0',
          'gunstyle':'0',
          'pot':nowtm,
          'ipaddress':'127.0.0.1'
        },
        merge: true
      },
      thingTypeName:process.env.Default_ThingType
    };
    try {
      await iotclient.send(new CreatePolicyCommand(createPolicyParams));
      await iotclient.send(new CreateThingCommand(createThingParams));
      await save_NEWChargerID_ENV();
    } catch (err) {
      console.error(err);
    }
    return await createCert(mac);
};
  
let reissueCert = async ( mac, certificateArn ) => {
    let detachPolicyParams = {
      policyName: mac,
      target: certificateArn
    };
    let detachThingParams = {
      thingName: mac,
      principal: certificateArn
    };
    try {
      await iotclient.send(new DetachPolicyCommand(detachPolicyParams));
      await iotclient.send(new DetachThingPrincipalCommand(detachThingParams));
      let certificateId = certificateArn.split('/')[1];
      let updateCertParams = {
        certificateId: certificateId,
        newStatus: 'INACTIVE'
      };
      await iotclient.send(new UpdateCertificateCommand(updateCertParams));
      let deleteCertParams = {
        certificateId: certificateId,
        forceDelete: false
      };
      await iotclient.send(new DeleteCertificateCommand(deleteCertParams));
    } catch (err) {
      console.error(err);
    }
    return await createCert(mac);
};

export const mainHandler = async (payload, context) => {

    console.info(JSON.stringify(payload));
    
    // console.time('xnapp');

    if (payload.connevent) {

/*****************    device connected message (poweron or reconnect)    ****************/
        let mac = payload.connevent;
        let iotdata = undefined;
        let thingtypename = '';
        try {
          iotdata = await iotclient.send(new DescribeThingCommand({thingName:mac}));
          thingtypename = iotdata.thingTypeName;
        } catch (err) {
        }
        if ( process.env.Default_ThingType != thingtypename )
        {
          return;
        }
        let eventType = payload.eventType;
        if (eventType=='connected') {   //connect/disconnect基本上是同时调用，前后相差不超过100ms
            if (mac.length==12) {
              let precnts = Number(iotdata.attributes.connected);
              let chargerid = Number(iotdata.attributes.chargerid);
              if ( isNaN(precnts) ) precnts = 0;
              if ( process.env.Default_ThingType == thingtypename )
              {
                precnts++;
                let nowtm = moment(new Date().getTime()).tz(process.env.Time_Zone).format(config.SF);
                let updatethingParams = {
                  thingName: mac,
                  attributePayload: {
                    attributes: {
                      'ipaddress':payload.ipAddress,
                      'onltime':nowtm,
                      'offtime':'0',
                      'connected':precnts.toString(),
                      'sessionid': payload.sessionIdentifier
                    },
                    merge: true
                  }
                };
                if ( chargerid == 1 ) {
                  updatethingParams.attributePayload.attributes.chargerid = '2';
                } else if ( chargerid == 2 ) {
                  updatethingParams.attributePayload.attributes.chargerid = '3';
                } else if ( chargerid == 3 ) {
                  updatethingParams.attributePayload.attributes.chargerid = '2';
                }
                try {
                  await iotclient.send(new UpdateThingCommand(updatethingParams));
                } catch (err) {
                  console.error(err);
                }
              }
            }
        } else if (eventType=='disconnected') {
          if (mac.length==12 && mac!='xniotevesps2') {
            // let connected = 1;
            // let chargerid = 0;
            // let sessionid = '';
            // let nowtm = moment(new Date().getTime()).tz(process.env.Time_Zone).format(config.SF);
            // try {
            //   let iotdata = await iotclient.send(new DescribeThingCommand({thingName:mac}));
            //   connected = Number(iotdata.attributes.connected);
            //   chargerid = Number(iotdata.attributes.chargerid);
            //   sessionid = iotdata.attributes.sessionid;
            // } catch (err) {
            //   console.error(err);
            // }
            // if ( sessionid == payload.sessionIdentifier && connected > 0 && ( chargerid == 0 || chargerid > 100 ) ) {
            //   let updatethingParams = {
            //     thingName: mac,
            //     attributePayload: {
            //       attributes: {
            //         'offtime':nowtm,
            //         'onltime':'0',
            //         'connected':'0'
            //       },
            //       merge: true
            //     }
            //   };
            //   try {
            //     await iotclient.send(new UpdateThingCommand(updatethingParams));
            //   } catch (err) {
            //     console.error(err);
            //   }
            // }
            let msgbody = {
              msgtype: 0,
              mac:mac
            };
            let arnarr = context.invokedFunctionArn.split(':');
            let SQSURL = config.SQSURLTEMPLATE.replace(/REGION/,arnarr[3]).replace(/ACCOUNTID/,arnarr[4]);
            let qeparams = {
              DelaySeconds: 30,
              MessageBody:JSON.stringify(msgbody),
              QueueUrl: SQSURL
            };
            await sqsclient.send(new SendMessageCommand(qeparams));
          }
        } else {
            console.error('unknown', JSON.stringify(payload));
        }

    }
    
    if (payload.reportmac) {

/*****************  mqtt rule message (defined in template.yaml IoTRule)  ****************/
        let mac = payload.reportmac;

        if (payload.getcert && mac=='xniotevesps2') {
/*****************  first connect to awsiot, Using default certificate  ****************/
          console.log('==================');
          let wifimac = payload.wifimac;
          let gunstyle = payload.gun;
          let fmver = payload.ver;
          let imax = '32,32';
          let hfconst = '64,64';
          if ( gunstyle == 1 ) {
            imax = '0,32';
            hfconst = '0,64';
          } else if ( gunstyle == 2 ) {
            imax = '32,0';
            hfconst = '64,0';
          }
          gunstyle = ''+gunstyle;
          let data = await listPolicyPrincipals( wifimac );
          let certArn = false;
          if (data && data.principals && data.principals.length>0) {
            certArn = data.principals[0];
          }
          let certData;
          if (certArn) {
            certData = await reissueCert(wifimac,certArn);
          } else {
            let arnarr = context.invokedFunctionArn.split(':');
            certData = await issueCert(wifimac, arnarr[3], arnarr[4]);
          }
          let certpem;
          let privkey;
          if ( fmver > '2.3.3' ) {
            certpem = certData.certificatePem;
            privkey = certData.keyPair.PrivateKey;
          } else {
            certpem = certData.certificatePem.replace(/\n/g,'').replace(/-----.*?-----/g,'');
            privkey = certData.keyPair.PrivateKey.replace(/\n/g,'').replace(/-----.*?-----/g,'');
          }
          let getcertpayload = {'devcert':certpem};
          let pubparam = {
            topic: config.MQTTWORK+mac,
            payload: Buffer.from(JSON.stringify(getcertpayload)),
            qos: 1
          };
          try {
            await iotdataclient.send(new PublishCommand(pubparam));
          } catch (err) {
            console.error(err);
          }
          getcertpayload = {'prvkey':privkey};
          pubparam = {
            topic: config.MQTTWORK+mac,
            payload: Buffer.from(JSON.stringify(getcertpayload)),
            qos: 1
          };
          try {
            await iotdataclient.send(new PublishCommand(pubparam));
          } catch (err) {
            console.error(err);
          }
          getcertpayload = {'newmac':wifimac};
          pubparam = {
            topic: config.MQTTWORK+mac,
            payload: Buffer.from(JSON.stringify(getcertpayload)),
            qos: 1
          };
          try {
            await iotdataclient.send(new PublishCommand(pubparam));
          } catch (err) {
            console.error(err);
          }
          let precertcnts = 0;
          try {
            let iotdata0 = await iotclient.send(new DescribeThingCommand({thingName:'xniotevesps2'}));
            precertcnts = Number(iotdata0.attributes.certcnts);
            if ( isNaN(precertcnts) ) precertcnts = 0;
          } catch (err) {
            console.error(err);
          }
          precertcnts++;
          let nowtm = moment(new Date().getTime()).tz(process.env.Time_Zone).format(config.SF);
          let updatethingParams = {
            thingName: 'xniotevesps2',
            attributePayload: {
              attributes: {
                'lastmac': wifimac,
                'certcnts': ''+precertcnts,
                'certime': nowtm
              }
            }
          };
          try {
            await iotclient.send(new UpdateThingCommand(updatethingParams));
          } catch (err) {
            console.error(err);
          }
          updatethingParams = {
            thingName: wifimac,
            attributePayload: {
              attributes: {
                'imax': imax,
                'hfconst': hfconst,
                'gunstyle': gunstyle
              },
              merge: true
            }
          };
          try {
            await iotclient.send(new UpdateThingCommand(updatethingParams));
          } catch (err) {
            console.error(err);
          }
        }
        
        // iotdata is globa var for all code next
        let iotdata = undefined;
        let thingtypename = '';
        let online = 0;
        try {
          iotdata = await iotclient.send(new DescribeThingCommand({thingName:mac}));
          online = Number(iotdata.attributes.onltime);
          thingtypename = iotdata.thingTypeName;
        } catch (err) {
          console.error(err);
        }
        if ( process.env.Default_ThingType != thingtypename )
        {
          return;
        }
        
        if (payload.guc) {
/*****************  gunchange message (to be implementioned) ****************/
        }
        
        // in "eventType=='disconnected'" a "taskid0" SQS message send after 30 seconds, 
        // and then make device offline, after that a "olreq" MQTT message will send to device to confirm offline
        // at here, if olresp recieved, the device will online again!
        if (payload.olresp) {
          if (online==0) {
            let nowtm = moment(new Date().getTime()).tz(process.env.Time_Zone).format(config.SF);
            let updatethingParams = {
              thingName: mac,
              attributePayload: {
                attributes: {
                  'onltime':nowtm,
                  'offtime':'0'
                },
                merge: true
              }
            };
            try {
              await iotclient.send(new UpdateThingCommand(updatethingParams));
            } catch (err) {
              console.error(err);
            } 
          }
        }

        if (payload.firstboot) {
          //{\"firstboot\":1,\"wifimac\":\"%.*s\",\"ver\":\"%1d.%1d.%1d\",\"dbg\":%1d,\"pon\":%10ld,swk:111,gun:1}
          let nowtm = moment(new Date().getTime()).tz(process.env.Time_Zone).format(config.SF);
          let fmver = payload.ver;
          let debug = payload.dbg;
          let pon = payload.pon;
          let swk = payload.swk;// switch state:1=Plug and Play, 2=CardReg, 4=NOBEEPwhen GND error
          let updatethingParams = {
            thingName: mac,
            attributePayload: {
              attributes: {
                'pot':nowtm,
                'pon':pon.toString(),
                'fmver':fmver,
                'swk':swk.toString(),
                'debug':debug.toString(),
                'onltime':nowtm,
                'offtime':'0',
                'connected':'1'
              },
              merge: true
            }
          };
          try {
            await iotclient.send(new UpdateThingCommand(updatethingParams));
          } catch (err) {
            console.error(err);
          }
          let imax = iotdata.attributes.imax.split(',');
          let hfconst = iotdata.attributes.hfconst.split(',');
          for (let i=0;i<imax.length;i++) {
            imax[i] = Number(imax[i]);
            hfconst[i] = Number(hfconst[i]);
          }
          let chargerid = iotdata.attributes.chargerid;
          let numchargerid = Number(iotdata.attributes.chargerid);
          while (chargerid.length<6) {
            chargerid = '0'+chargerid;
          }
          let outjson = {'firstboot':{'limit':imax,'chargerid':chargerid,'hfconst':hfconst/*,'pwmstyle':1*/}};
          let pubparam = {
            topic: config.MQTTWORK+mac,
            payload: Buffer.from(JSON.stringify(outjson)),
            qos: 1
          };
          try {
            await iotdataclient.send(new PublishCommand(pubparam));
          } catch (err) {
            console.error(err);
          }
          // OTAUpdate: chargerid=1,2,3 means updating
          if ( ( ( fmver == process.env.OTx_Version ) || ( fmver == process.env.OTA_Version ) ) && numchargerid > 0 && numchargerid < 100 ) {
            // connected always early than firstboot
            // in connected do not change chargerid to 0，and in here "firstboot" change chargerid to 0
            // console.log('cleanOTAUpdate======================');
            try {
              await iotclient.send(new DeleteOTAUpdateCommand({otaUpdateId:mac,deleteStream:true,forceDeleteAWSJob:true}));
            } catch (err) {
              console.error(err);
            }
            // console.log('restoreChargerid======================');
            // set new chargerid after upgrade
            let updatethingParamc = {
              thingName: mac,
              attributePayload: {
                attributes: {
                  'onltime':moment(new Date().getTime()).tz(process.env.Time_Zone).format(config.SF),
                  'offtime':'0',
                  'connected':'1',
                  'chargerid':process.env.NEW_ChargerID.toString()
                },
                merge: true
              }
            };
            try {
              await iotclient.send(new UpdateThingCommand(updatethingParamc));
              await save_NEWChargerID_ENV();
            } catch (err) {
              console.error(err);
            }
          }
        }

    }
    
    // console.timeEnd('xnapp');

};
